home *** CD-ROM | disk | FTP | other *** search
- Path: chronicle.mti.sgi.com!austern
- From: "ian (i.) willmott" <willmott@bnr.ca>
- Newsgroups: comp.std.c++
- Subject: Generic Object Callbacks
- Date: 22 Feb 1996 15:30:31 PST
- Organization: -
- Approved: austern@isolde.mti.sgi.com
- Message-ID: <pgpmoose.199602221531.14635@isolde.mti.sgi.com>
- NNTP-Posting-Host: isolde.mti.sgi.com
- X-Original-Date: Tue, 20 Feb 1996 01:37:00 -0500
- Content-Identifier: Generic Objec...
- X-Auth: PGPMoose V1.1 PGP comp.std.c++
- iQBVAwUBMSz8uky4NqrwXLNJAQHELQH/fmc4KooMLMLXV2O9kZhZhH8xIa+UYwhU
- oQT0C8j+A2Xi+BeOjTwu6fRmfc0d3NkYKrO7f0vXsaEfEY7Apk74lQ==
- =zEk4
- Originator: austern@isolde.mti.sgi.com
-
- Subject: Generic Object Callbacks
-
- In an article entitled "Q: Generic Callbacks -- Object->*func(...)"
- (reference <4fti32$p3p@bcarh8ab.bnr.ca>), bcwhite@bnr.ca asks
-
- "Does the C++ standard allow for a generic callback to be specified?
- Basically, I'd like to be able to pass an arbitrary object and
- function of that object to be called at some later time."
-
- What is needed is the ability to use member functions as callbacks
- without any constraint on the type of the object they are invoked on.
- This is possible in C++ only by using various implementation-dependent
- hacks to escape the type system, because the language does not provide
- any way to express such a construct.
-
- This is strange, because the use of callbacks is critical to
- event-driven programming for applications such as embedded systems and
- graphical interfaces; C++, which aims to support object-oriented
- programming, ought to allow member functions as well as ordinary
- functions to be the target of a callback. As yet, it doesn't.
-
- I suggest that a new data type needs to be added to the language. We
- could call it "pointer-to-bound-member-function". A small example will
- clarify the requirement:
-
- int *pi;
- int (*pf)(int);
-
- void f1(int i)
- {
- *pi=i;
- (*pf)(i);
- }
-
- // somewhere else:
-
- class A {
- public:
- int x;
- int f(int);
- };
-
- void f2(A *pa)
- {
- pi=&pa->x; // this is legal
- pf=&pa->f; // this isn't
- }
-
- The desired semantics here are perfectly clear. Assuming f2() has been
- executed first, the second line in f1() should invoke A::f() on the
- object referenced by f2's argument, without having to know anything
- about the object type. This is exactly analogous with the integer
- pointer pi, which is why I included it.
-
- The reason this isn't legal is because of the way function pointers
- are implemented, as a single machine pointer into code space. To make
- the example work, a pointer into data space, to identify the object,
- would have to be saved as well. It would be a bad idea to burden the
- implementation of function pointers with an extra word of storage just
- to make this work, so a new type is needed to provide this facility.
-
- The name "pointer to bound member function" is suggested by the error
- message Cfront produces when you feed it the code above. Specifically,
- the type I am proposing will encapsulate the idea of a member function
- callback in a completely typesafe way, and will be trivially easy to
- implement. Conceptually, it is a two-element structure consisting of
- pointer into data space, identifying an object, and a pointer into
- code space, identifying a member function on that object. The static
- type of such a variable is the signature (return type and arguments)
- of the member functions it is compatible with, just as the type of a
- regular function pointer is the signature of the functions it can
- reference. The only operations defined for this type are assignment,
- equality, and callthrough. Static typechecking is done at the point
- where a value of this type is created, and where a call is made
- through it.
-
- Notice that this type is already implicit in the language. In the
- expression
-
- (pa->f)(17)
-
- what is the type of the subexpression (pa->f) ? The parentheses are
- not even necessary; the operator associativity produces the same
- binding. This means that C++ contains legal expressions whose type
- cannot be expressed in the language. I propose that the language be
- extended so that this type can be expressed, variables of this type
- can be declared, and the obvious operations on them provided.
-
- This is what is needed:
- 1 - declaration syntax for objects of this type
- 2 - expressions such as the one above are values of this type
- 3 - assignment operator for variables of this type
- 4 - call through
- 5 - implicit conversion from zero
- 6 - implicit conversion from signature-compatible pointers-to-function
- 7 - equality and inequality operators for values of this type
-
-
- Declaration Syntax
-
- No new keywords are required. A slight modification of the existing
- pointer-to-member-function declarator syntax will suffice.
-
- return-type (class::*pbmf)(arg-decl-list)
-
- declares the identifier pbmf to be a pointer-to-bound-member-function
- with the specified signature. "struct" should probably be allowed in
- place of "class". Similar syntax will be used to declare pointers and
- references to objects of this type, arrays of this type, functions
- returning this type, function arguments of this type, etc.
-
- If this syntax is inappropriate for some reason, something similar
- could be devised instead.
-
-
- Values of Type Pointer-to-Bound-Member-Function
-
- Given the declaration of class A above, and the following declarations
-
- A obj,&ref,*ptr;
- int (A::*pmf)(int);
-
- the following expressions all have type
- "pointer-to-bound-member-function of one int parameter returning int":
-
- obj.f
- ref.f
- ptr->f
- obj.*pmf
- ref.*pmf
- ptr->*pmf
-
- Anywhere it is possible to call a non-static class member function on
- an object, it is possible to compute two things: the entry point of
- the actual function to be executed, and the object pointer to be
- passed to that function. Computing the function entry point may
- involve virtual function table lookup, and in the presence of multiple
- inheritance the object pointer passed to the function may not be the
- same as the address of the object on which the function was called.
- The pair of these values constitute a value of type
- pointer-to-bound-member-function. This value can be used immediately
- to specify a member function call, or it can be saved for later use.
- If this happens, information about the type of the object need not be
- preserved, because all typechecking except of the actual parameters
- used in the call has already been done.
-
- If an expression of one of the first three forms above refers to a
- static member function, the data-pointer component of the resulting
- pointer-to-bound-member-function will have the value used by the
- implementation to represent a null pointer.
-
-
- Assignment
-
- Assignment to an lvalue of type pointer-to-bound-member-function is
- defined to mean copying of the two components of the value on the
- right-hand-side of the assignment expression into the corresponding
- elements of the lvalue.
-
-
- Call Through
-
- If the identifier pbmf denotes a pointer-to-bound-member-function, and
- the arguments supplied in an expression of the form
-
- (*pbmf)(arg-list)
-
- match the function signature of pbmf, then the expression specifies a
- function call through the value contained in pbmf, the type of the
- expression is the return type of pbmf, and the value of the expression
- is the value returned by the called function.
-
- If the data-pointer component of the value of pbmf is non-null, it
- will be passed to the called function using the usual mechanism for
- object-pointers in member function calls; if it is null, it will not
- be. In this most implementations, this will mean that it will be
- pushed onto the stack after all the other parameters only if it is
- non-zero. The code-pointer component of the value of pbmf specifies
- the exact entry point of the function to be executed. If it is null,
- the resulting behaviour is undefined.
-
- In order to match the syntax for calls through regular
- pointers-to-function, the expression
-
- pbmf(arg-list)
-
- should be allowed and have the same meaning.
-
-
- Implicit Conversions
-
- A value of type pointer-to-function can be implicitly converted to a
- pointer-to-bound-member-function of the same signature. The
- code-pointer component of the resulting PBMF has the same value as the
- pointer-to-function, and the data-pointer component of the PBMF has
- whatever value the implementation uses to represent null data
- pointers. This implies that there is an implicit conversion to PBMF
- from zero, since zero can be implicitly converted to
- pointer-to-function.
-
-
- Equality and Inequality Operators
-
- Two values of the same pointer-to-bound-member-function type may be
- compared to each other using the == and != operators. This is defined
- to mean component-wise comparison of the two values, which are
- considered to be equal only if the corresponding components are both
- equal, and not equal otherwise.
-
-
- General Comments
-
- For reasons of syntactic consistency, declarations such as the
- following should probably be allowed:
-
- int class::*pbmd;
-
- Objects declared this way will have the same semantics and
- implementation as normal data pointers and there should be implicit
- conversions in both directions between them.
-
- PBMFs are upward compatible with regular pointers-to-function;
- anywhere a callback might have to refer to member function on an
- object, it should be declared as a PBMF; either type can then be
- assigned to it, and the callthrough will behave appropriately. Where
- this facility is not needed, pointers-to-function will suffice.
-
- The proposed type pointer-to-bound-member-function introduces to C++
- no new keywords, only one new piece of syntax, the declarator, and one
- new run-time operation, the callthrough, consisting of a conditional
- stack push and a function call. It is statically type-checked in
- exactly the same way as a regular member function call; it merely
- separates member function lookup and invocation. Objects of this type
- are opaque; there is no way to access the two components independently
- and consequently it is completely type-safe, something that cannot be
- said for the kludges currently necessary to implement the
- functionality it provides. This datatype can be implemented in two
- machine words on linear address-space architectures and requires no
- additional run-time support. It appears to be completely orthogonal to
- the rest of the C++ language.
-
-
- Liabilities
-
- I can think of only one possible problem. If a PBMF referring to a
- virtual function is exported from a base class constructor invoked for
- a derived class object, then when a call through that PBMF is made,
- the member function invoked will be the one defined for the base class
- and not the derived class. No simple implementation can prevent this
- possibility. It's pretty hard to think of any good reason why you
- would want to do this. This is analogous to the situation of calling a
- pure virtual function from a base class constructor, although at least
- that can be detected at runtime. There is the corresponding
- possibility of importing a PBMF into a base class destructor, but
- that's even more unlikely. Any function pointers used in code called
- from constructors and destructors would be much better expressed as
- pointers-to-member-function.
-
-
- Possible Objections
-
-
- "You can do this with an object pointer and a pointer-to-member-function."
-
- Only if all of your callbacks are to objects in the same class
- hierarchy (a severe restriction), and to members declared in exactly
- the same class. It's not even good enough that they be declared in
- classes derived from a common base type, since if D is derived from B,
- you can assign an value of type (B::*)() to an object of type
- (D::*)(), but not the other way around. There's a good reason for this
- rule: if it didn't exist it would be possible to call a member
- function on an object of a class for which it was not defined.
-
- This points out the need for a type which encapsulates an object
- reference and a member function reference so that there is no
- possibility of applying the function reference to an object of
- inappropriate type.
-
-
- "There are other ways to do this."
-
- There are two that I know of.
-
- class pbmf {
- void *ptr;
- int (*func)(void *,int)
- public:
- pbmf(void *p,int (*f)(void *,int)):
- ptr(p),func(f) {}
- int operator()(int x)
- { return(func(ptr,x)); }
- };
-
- int Af(void *p,int x)
- {
- return(((A *) p)->f(x));
- }
-
- A obj;
- pbmf pf(&obj,&Af);
-
- pf(17);
-
- This isn't typesafe, it requires you to rewrite the class declaration
- for every new function signature, and to write a wrapper function
- around every member function you want to use this way, and imposes the
- overhead of an extra function call.
-
- class pbmfbase {
- public:
- virtual int operator()(int);
- };
-
- template<class T> class pbmf: public pbmfbase {
- T *ptr;
- int (T::*func)(int);
- public:
- pbmf(T *p,int (T::*f)(int))
- ptr(p),func(f) {}
- int operator()(int x)
- { return(ptr->*func(x)); }
- };
-
- pbmfbase *pf=new pbmf<A>(&obj,&A::f);
-
- (*pf)(17);
-
- This requires a new template for every different function signature
- (you might be able to do something with member function templates), it
- costs an extra virtual function call, and because the callback has to
- be done through a pointer or reference, objects of this type are
- probably going to end up on the heap, although you might be able to
- avoid this with some other type-unsafe tricks. Using templates is
- really overkill for an application this simple.
-
- These are really ugly hacks to accomplish something that should be
- directly supported by the language. The fact that the type
- pointer-to-bound-member-function is implicit but unexpressable in the
- language shows that this is a real deficiency.
-
-
- "Any built-in type should fit in a single processor register."
-
- According to the discussion in the ARM and the D&E, pointers to member
- function are represented by structures with at least three elements.
- PBMFs are much easier to implement than pointers-to-member-function.
-
- Also, floating point numbers often do not fit into a single register on
- machines without hardware floating point support.
-
-
- "What will happen if a PBMF is called through after the object it
- refers to has been destroyed?"
-
- This is the same situation as calling a member function through a
- regular pointer which does not reference a valid object, either
- because the pointer is invalid, or the object has been destroyed. The
- language does not prevent you from doing this.
-
-
- "It's not type-safe."
-
- PBMFs are statically type-checked in exactly the same way that regular
- member function calls are. They simply separate the object
- type-checking from function invocation. Once member function lookup
- and pointer adjustment are done, there is no further need for object
- type information.
-
-
- I realize that the probability of any more extensions to the standard
- being accepted at this point is small, but I thought that it would be
- worth discussing this one anyway. I would not be at all surprised to
- hear that something similar to this has been proposed before, although
- I saw no mention of any such in the D&E. If so, I would like to know
- why it was rejected.
-
- I would like to know how many people think that this would be a useful
- language feature. It seems to me that it would make object-oriented,
- event-driven programming much easier. I have been working on the
- embedded control software for a network switch for the last two years
- and the need for this facility has arisen over and over again, but I
- have had to make do with the sort of kludges listed above. When the
- GNU C++ compiler becomes more stable, I will look into adding this
- datatype to it. All comments are welcome.
-
-
- Ian Willmott
- Bell-Northern Research
- Ottawa, Ontario
- (613)-763-9688
- willmott@bnr.ca
- ---
- [ To submit articles: Try just posting with your newsreader. If that fails,
- use mailto:std-c++@ncar.ucar.edu
- FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html
- Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html
- Comments? mailto:std-c++-request@ncar.ucar.edu
- ]
-